use crate::entity::sys_role::*;
use axum_app_common::model::PageResult;
use axum_app_macro::QueryPageBuilder;
use sea_orm::{sea_query::Expr, *};
use tracing::{error, info};

#[derive(Debug, QueryPageBuilder)]
pub struct RoleQuery {}

impl RoleQuery {
    pub async fn find_role_by_code<C>(db: &C, code: &str) -> anyhow::Result<Option<Model>>
    where
        C: ConnectionTrait,
    {
        let res = Entity::find()
            .filter(Column::RoleCode.eq(code))
            .one(db)
            .await?;
        Ok(res)
    }
}

impl RoleQueryCondition {
    pub fn deleted(mut self, deleted: bool) -> Self {
        if deleted {
            self.filter = self.filter.add(Column::DeleteFlag.lt(0));
        } else {
            self.filter = self.filter.add(Column::DeleteFlag.eq(0));
        }
        self
    }

    pub fn role_code_eq(mut self, role_code: Option<&str>) -> Self {
        if role_code.filter(|s| !s.trim().is_empty()).is_some() {
            let code = role_code.unwrap();
            self.filter = self.filter.add(Column::RoleCode.eq(code));
        }
        self
    }

    pub fn role_name_like(mut self, role_name: Option<&str>) -> Self {
        if role_name.filter(|s| !s.trim().is_empty()).is_some() {
            let keyword = format!("%{}%", role_name.unwrap());
            self.filter = self.filter.add(Column::RoleName.like(keyword));
        }
        self
    }
}

pub async fn insert_role<C>(db: &C, role: ActiveModel) -> anyhow::Result<u64>
where
    C: ConnectionTrait,
{
    let res = Entity::insert(role).exec(db).await?;
    Ok(res.last_insert_id as u64)
}

pub async fn insert<C>(db: &C, code: &str, name: &str, description: &str) -> anyhow::Result<u64>
where
    C: ConnectionTrait,
{
    let role = ActiveModel {
        role_code: Set(code.to_string()),
        role_name: Set(name.to_string()),
        role_description: Set(description.to_string()),
        delete_flag: Set(0),
        ..Default::default()
    };
    insert_role(db, role).await
}

pub async fn update_by_code<C>(
    db: &C,
    code: &str,
    name: Option<&str>,
    description: Option<&str>,
) -> anyhow::Result<u8>
where
    C: ConnectionTrait,
{
    if name.is_none() && description.is_none() {
        return Ok(0);
    }

    let name_v = name.map(|s| Box::new(s.to_string()));
    let desc_v = description.map(|s| Box::new(s.to_string()));

    let res = Entity::update_many()
        .filter(Column::RoleCode.eq(code))
        .col_expr(Column::RoleName, Expr::value(Value::String(name_v)))
        .col_expr(Column::RoleDescription, Expr::value(Value::String(desc_v)))
        .exec(db)
        .await?;

    Ok(res.rows_affected as u8)
}

pub async fn update_by_id<C>(db: &C, id: u64, model: ActiveModel) -> anyhow::Result<Model>
where
    C: ConnectionTrait,
{
    let act_model = ActiveModel {
        id: Set(id as i64),
        ..model
    };
    let res = Entity::update(act_model).exec(db).await?;

    Ok(res)
}

#[cfg(test)]
mod tests {
    use axum_app_common::{error, test::get_connect};
    use sea_orm::{DbErr, Set};

    use super::*;

    #[tokio::test]
    async fn test_insert() {
        let db = get_connect().await;
        let res = insert(&db, "ADMIN", "管理员", "管理员角色").await;

        println!(
            "is duplicate: {:?}",
            error::is_duplicate_exception::<_>(&res)
        );
    }

    #[tokio::test]
    async fn test_find_role_by_code() {
        let db = get_connect().await;
        let res = RoleQuery::find_role_by_code(&db, "ADMIN").await;
        println!("res: {:?}", res);
    }
}
